
#include "nodo.h"

unsigned short Nodo::BAD_REF = USHRT_MAX;

Nodo::Nodo (Bloque* bloque, TipoDato* clave_base, TipoDato* dato_base){
	this->bloque = bloque;
	this->clave_base = clave_base->clonar();
	this->dato_base = dato_base->clonar();
	this->primero(); //Posiciono al nodo en el primer elemento
	
	//Si el bloque esta vacio, le meto bad_ref a la 1era pos izq y al nivel
	unsigned short int bad_ref = BAD_REF;
	if (this->bloque->get_tope() == 0) {
		this->set_nivel(bad_ref);
		this->bloque->escribir((char*) &bad_ref, TAM_REF, TAM_REF);
	}
}

Nodo::Nodo (Bloque* bloque, TipoDato* clave_base){
	this->bloque = bloque;
	this->clave_base = clave_base->clonar();
	this->dato_base = NULL; //No tiene dato, esto es null
	this->primero();
	
	//Si el bloque esta vacio, le meto bad_ref a la 1era pos izq y al nivel
	unsigned short int bad_ref = BAD_REF;
	if (this->bloque->get_tope() == 0) {
		this->set_nivel(bad_ref);
		this->bloque->escribir((char*) &bad_ref, TAM_REF, TAM_REF);
	}
}

Nodo::~Nodo(){
	if (this->clave_base != NULL)
		delete (this->clave_base);
	if (this->dato_base != NULL)
		delete (this->dato_base);
}
		
unsigned short int Nodo::get_nivel() {
	//Leo el nivel desde el bloque en la pos relativa 0.
	unsigned short int resultado = Nodo::BAD_REF;
	this->bloque->leer((char*) &resultado, 0, TAM_REF);
	return resultado;
}

void Nodo::set_nivel(unsigned short int nivel) {
	//Escribo el nivel en el bloque en la pos relativa 0.
	this->bloque->escribir((char*) &nivel, 0, TAM_REF);
}
		
unsigned int Nodo::get_esp_libre_nodo() {
	return this->bloque->get_espacio_libre();	
}

void Nodo::primero() {
	//Cambio la pos actual al primero
	this->pos_act = PRIMERO;	
}

void Nodo::ultimo() {
	this->primero();
	while(this->siguiente());
}
		
bool Nodo::get_clave(TipoDato* clave) {
	unsigned int tam = this->get_tam_clave_actual();
	char* serializado = NULL;
	
	//Si esta en una pos valida
	if (this->pos_valida()) {
		//Leo desde el bloque la clave serializada, deserializo y devuelvo true
		serializado	= new char[tam];
		this->bloque->leer(serializado, pos_act, tam);
		clave->deserializar(serializado, tam);
		delete[](serializado);
		return true;
	}
	return false;
}
		
bool Nodo::get_dato(TipoDato* dato){
	char* serializado = NULL;
	unsigned int tam_clave = this->get_tam_clave_actual();
	unsigned int tam_dato = this->get_tam_dato_actual();
	
	//Si esta en una pos valida
	if (this->pos_valida()) {
		//Leo desde el bloque el dato serializado, deserializo y devuelvo true
		serializado	= new char[tam_dato];
		this->bloque->leer(serializado, pos_act + tam_clave, tam_dato);/**REVISAR**/
		dato->deserializar(serializado, tam_dato);
		delete[](serializado);
		return true;	
	}
	return false;
}
		
unsigned short Nodo::get_ref_izq() {
	unsigned short int resultado = Nodo::BAD_REF;
	//Si el nodo no es hoja, leo la ref izq al elem actual
	if (!this->es_hoja())
		this->bloque->leer((char*) &resultado, pos_act - TAM_REF, TAM_REF);
	return resultado;
}
		
unsigned short Nodo::get_ref_der(){
	unsigned short int resultado = Nodo::BAD_REF;
	unsigned int tam = this->get_tam_clave_actual();
	unsigned int tam_dato = this->get_tam_dato_actual();
	//Si el nodo no es hoja y estoy parado sobre un elem, devuelvo la ref derecha
	if ((!this->es_hoja()) && (this->pos_valida()))
		this->bloque->leer((char*) &resultado, pos_act + tam + tam_dato, TAM_REF);
	return resultado;
}
		
bool Nodo::siguiente() {
	//Guardo la pos actual
	unsigned int pos_aux = this->pos_act;
	unsigned int tam = this->get_tam_elem_actual();
	//Si estoy sobre un elemento
	if (this->pos_valida()) {
		//Incremento la pos actual
		pos_aux += tam;
		//Si cai dentro del nodo actualizo la pos actual, sino devuelvo false
		if (pos_aux < this->bloque->get_tope())
			this->pos_act = pos_aux;
		else
			return false;
	} else
		return false;
	return true;
}
		
bool Nodo::anterior() {
	//Si es el primer elem devuelve false
	if (this->pos_act <= PRIMERO)
		return false;
	//Sino guardo la pos act y me voy al principio
	unsigned int pos_aux = this->pos_act;
	unsigned int tam = 0;
	this->primero();
	tam = this->get_tam_elem_actual();
	//Mientras el siguiente no sea el actual anterior, avanzo
	while ((this->pos_act + tam) < pos_aux) {
		this->pos_act += tam;
		tam = this->get_tam_elem_actual();
	}
	return true;
}
		
bool Nodo::ingresar_elemento(TipoDato* clave, TipoDato * dato){
	//Busco el elemento, si esta devuelvo false porq las claves son unicas
	bool ingresado = this->buscar_elemento(clave);	
	if (ingresado) {
		this->primero();
		return false;
	}
	
	TipoDato* clave_actual = NULL;
	unsigned int tam_actual = 0;
	
	//Si no esta vacio me fijo si deberia ir a lo ultimo, y si es asi calculo cuanto me deberia correr
	if (!this->vacio()) {
		clave_actual = clave_base->clonar();
		this->get_clave(clave_actual);
		if ( (*clave_actual) < (*clave))
			tam_actual = this->get_tam_elem_actual();
		delete (clave_actual);
		pos_act += tam_actual;
	}
	
	//Agrego el elemento al nodo si entra
	if (pos_act == PRIMERO)
		ingresado = ingresar_interno(clave, dato, true);
	else 
		ingresado = ingresar_interno(clave, dato, false);
	//Si no pude ingresar vuelvo al primero
	if (!ingresado) this->primero();
	return ingresado;
}

void Nodo::set_ref_izq(unsigned short ref_izq){
	if (!this->es_hoja())
		this->bloque->escribir((char*) &ref_izq, pos_act - TAM_REF, TAM_REF);	
}
		
void Nodo::set_ref_der(unsigned short ref_der){
	if (!this->es_hoja()){
		unsigned int tam = this->get_tam_elem_actual();
		if (this->pos_valida())
			this->bloque->escribir((char*) &ref_der, pos_act + tam - TAM_REF, TAM_REF);
	}
}

bool Nodo::buscar_elemento (TipoDato* clave){
	//Si el nodo no esta vacio
	if (!this->vacio()) {
		//Clono la clave base, me muevo al principio
		TipoDato* aux = this->clave_base->clonar();
		bool encontrado = false;
		bool ok = true;
		this->primero();
		//Busco hasta que lo encuentre o lea uno mayor. Si llegue al final termino.
		do {
			ok = this->get_clave(aux);
			if ((ok) && ((*clave) == (*aux))) {
				encontrado = true;
				ok = false;	
			} else
				if ((ok) && ((*clave) < (*aux)))
					ok = false;
			if (ok) 
				ok = this->siguiente();
			delete(aux);
			aux = this->clave_base->clonar();
		} while (ok);
		delete (aux);
		//Devuelvo si lo encontre o no
		return encontrado;
	}
	return false;
}

bool Nodo::quitar_elemento (TipoDato* clave){
	//Busco el elemento, si lo encontre me queda posicionado en el mismo
	bool encontrado = this->buscar_elemento(clave);
	if (encontrado) {
		//Obtengo el tamanio total del elemento y comprimo el bloque desde esa pos con ese tamanio
		unsigned int tam_total = this->get_tam_elem_actual();
		this->bloque->comprimir(pos_act, tam_total);
		//Si queda en una pos invalida, es porq el elemento era el ultimo, entonces va al ultimo elemento
		if (!this->pos_valida())
			this->ultimo();
	}	
	return encontrado;
}
		  
bool Nodo::vacio() {
	return (bloque->get_tope() <= PRIMERO);
}
		  
bool Nodo::es_hoja() {
	return (get_nivel() == 0);	
}

Bloque* Nodo::get_bloque(){
	return this->bloque;	
}

TipoDato * Nodo::get_clave_base(){
	return this->clave_base->clonar();
}

TipoDato * Nodo::get_dato_base(){
	return this->dato_base->clonar();	
}

bool Nodo::quitar_elemento(){
	TipoDato* clave;
	if (!this->vacio()) {
		clave = this->clave_base->clonar();
		this->get_clave(clave);
		quitar_elemento(clave);
		delete (clave);
		return true;
	}
	return false;
}

char Nodo::dividir_nodo(Nodo * nodo_nuevo, TipoDato ** clave, TipoDato ** dato) {
	char result; //Vble para almacenar el resultado numerico
	TipoDato* clave_ant; //Vble aux para almacenar la clave que desbordo
	TipoDato* clave_aux; //Vble aux para el manejo de claves
	TipoDato* dato_aux; //Vble aux para el manejo de datos
	unsigned short ref_aux;
	
	//Calculo el tamanio serializado del dato nuevo
	unsigned int tam_dato_nuevo = (*clave)->get_tamanio_serializado() + (*dato)->get_tamanio_serializado();
	//Si no es hoja sumo el de la ref derecha
	if (!this->es_hoja())
		tam_dato_nuevo += TAM_REF;
	
	nodo_nuevo->set_nivel(this->get_nivel()); //Asigno el mismo nivel al nodo nuevo
	
	Bloque* aux = this->bloque; //Guardo una ref al bloque actual
	//Creo un bloque con una zona de overflow e ingreso el dato nuevo y se lo asigno como bloque del nodo
	Bloque* bq_con_oflow = new Bloque(aux->get_num_bloque(), aux->get_size() + tam_dato_nuevo);
	char* contenido = new char[aux->get_tope()];
	aux->leer(contenido, 0, aux->get_tope());
	bq_con_oflow->vaciar();
	bq_con_oflow->escribir(contenido, 0, aux->get_tope());
	delete[] (contenido);
	this->bloque = bq_con_oflow;
	this->ingresar_elemento(*clave,*dato);
	
	//Voy a la mitad y cargo el elemento del medio del bq con overflow (bq actual)
	this->mitad();
	clave_ant = *clave; //almaceno la clave que inserte primero para despues
	*clave = this->clave_base->clonar();
	delete(*dato); 
	(*dato) = this->dato_base->clonar();
	this->get_clave(*clave);
	this->get_dato(*dato);
	ref_aux = this->get_ref_der();
	this->quitar_elemento(); //Quito el elem del medio despues de obtenerlo (es el actual)
	
	//recorro los elementos siguientes al del medio y los meto en el nodo nuevo
	bool ok = true;
	bool primero = true;
	do {
		clave_aux = this->clave_base->clonar();
		dato_aux = this->dato_base->clonar();
		this->get_clave(clave_aux);		
		this->get_dato(dato_aux);
		ok = ((**clave) < (*clave_aux));
		if (ok) {
			nodo_nuevo->ingresar_elemento(clave_aux, dato_aux);
			//Seteo las referencias del nuevo elemento en el nodo nuevo
			if (primero) {
				nodo_nuevo->set_ref_izq(this->get_ref_izq());
				primero = false;
			}
			nodo_nuevo->set_ref_der(this->get_ref_der());
			this->quitar_elemento();
		}
		delete(clave_aux); delete (dato_aux);
	} while (ok);
	
	//Seteo la 1era ref izq del nuevo nodo como la vieja derecha del medio
	nodo_nuevo->primero();
	nodo_nuevo->set_ref_izq(ref_aux);
	
	//Copio el bq con overflow al anterior y lo vuelvo a meter en el nodo para que quede como antes
	//pero con la mitad mas baja
	contenido = new char[bq_con_oflow->get_tope()];
	bq_con_oflow->leer(contenido, 0, bq_con_oflow->get_tope());
	aux->vaciar();
	aux->escribir(contenido, 0, bq_con_oflow->get_tope());
	delete[](contenido);
	this->bloque = aux;
	delete(bq_con_oflow);
	
	//Me fijo si la clave ingresada esta a la derecha, a la izq o es el elem del medio
	//Devuelvo el resultado acorde y acomodo las posiciones segun la interfaz
	if (this->buscar_elemento(clave_ant)) {
		result = -1;
		nodo_nuevo->primero();
	} else {
		if (nodo_nuevo->buscar_elemento(clave_ant)) {
			result = 1;
			this->ultimo();
		} else {
			result = 0;
			this->ultimo();
			nodo_nuevo->primero();	
		}
	}
	
	delete (clave_ant);
	return result; //Devuelvo el resultado
}

bool Nodo::unir(Nodo * nodo){
	unsigned int esp_libre = this->get_esp_libre_nodo();
	unsigned int esp_ocupado_nodo_param = nodo->get_bloque()->get_tope() - PRIMERO;
	bool ok = true;
	TipoDato* clave_aux;
	TipoDato* dato_aux;
	if (esp_ocupado_nodo_param <= esp_libre) {
		nodo->primero();
		ok = nodo->pos_valida();
		while (ok) {
			clave_aux = nodo->get_clave_base();
			dato_aux = nodo->get_dato_base();
			ok = nodo->pos_valida();
			if(ok){
				nodo->get_clave(clave_aux);		
				nodo->get_dato(dato_aux);
				this->ingresar_elemento(clave_aux, dato_aux);
				//Seteo las referencias del nuevo elemento en this
				//this->set_ref_izq(nodo->get_ref_izq());
				this->set_ref_der(nodo->get_ref_der());
				//Quito el elemento del nodo
				nodo->quitar_elemento();
			}
			delete(clave_aux); delete (dato_aux);
		}
		return true;
	}
	return false;
}

unsigned int Nodo::get_tam_clave_actual(){
	unsigned int resultado = 0;
	if (this->pos_valida())
		resultado = this->get_tam_dato(pos_act, this->clave_base->get_codigo_tipo());
	return resultado;
}

unsigned int Nodo::get_tam_dato_actual(){
	unsigned int resultado = 0;
	if ((this->pos_valida()) && (this->dato_base != NULL))
		resultado = this->get_tam_dato(pos_act  + this->get_tam_clave_actual(), this->dato_base->get_codigo_tipo());
	return resultado;
}
		
unsigned int Nodo::get_tam_elem_actual(){
	//Obtengo el valor del tamanio de la clave actual
	unsigned int resultado = this->get_tam_clave_actual();
	if (resultado != 0) {
		//Si existe el elem, sumo el del dato actual
		resultado += this->get_tam_dato_actual();
		//Si no es hoja sumo el de la ref derecha
		if (!this->es_hoja())
			resultado += TAM_REF;
	}
	return resultado;
}

unsigned int Nodo::get_tam_dato(unsigned int pos, CodTipo tipo){
	unsigned int resultado = 0;
	unsigned int aux = 0;
	//POR AHORA SOLO PARA INT Y STRING
	switch (tipo) {
		case INT:
			resultado = sizeof(int);
			break;
		case STRING:
			this->bloque->leer((char*) &aux, pos, sizeof(unsigned short int));
			resultado = aux;
			break;
		default:break;
	}
	return resultado;
}

bool Nodo::ingresar_interno(TipoDato* clave, TipoDato* dato, bool primero){
	char * s_clave; //Clave serializada
	char * s_dato = NULL; //Dato serializado
	bool ok = true;
	unsigned int tam_clave = 0;
	unsigned int tam_dato = 0;
	unsigned int tam_total = 0;
	unsigned int bad_ref = Nodo::BAD_REF;
	
	//Serializo la clave y el dato a insertar, calculo el tamanio total y me fijo si entra, expandiendo el bloque
	clave->serializar(&s_clave, tam_clave);
	if (this->dato_base != NULL) dato->serializar(&s_dato, tam_dato);
	tam_total = tam_clave + tam_dato;
	if (!this->es_hoja()) tam_total += TAM_REF;
 	ok = this->bloque->expandir(this->pos_act, tam_total);
	
	//Si entra escribo la clave, el dato y las referencias
	if (ok) {
		this->bloque->escribir(s_clave, pos_act, tam_clave);
		if (s_dato != NULL) 
			this->bloque->escribir(s_dato, pos_act + tam_clave, tam_dato);
		if (!this->es_hoja()) 
			this->bloque->escribir((char*) &bad_ref, pos_act + tam_clave + tam_dato, TAM_REF);

		if ((primero) && (!this->es_hoja()))
			this->bloque->escribir((char*) &bad_ref, pos_act - TAM_REF, TAM_REF);
	}
	//Borro los serializados
	delete[] s_clave;
	if (s_dato != NULL) delete[] s_dato;
	return ok;
}

void Nodo::mitad() {
	unsigned int media = this->bloque->get_tope() / 2;
	int dist_min = INT_MAX;
	unsigned int pos_min = UINT_MAX;
	int dist_aux = INT_MAX;
	if (!this->vacio()) {
		this->primero();
		do {
			dist_aux = this->pos_act - media;
			if (dist_aux < 0) dist_aux*=-1;
			if (dist_aux < dist_min){
				dist_min = dist_aux;
				pos_min = this->pos_act;
			}
		}
		while (this->siguiente());
		this->primero();
		while (this->pos_act != pos_min) this->siguiente();
	}
}

bool Nodo::pos_valida(){
	return (pos_act < this->bloque->get_tope());	
}

void Nodo::vaciar() {
	unsigned short int nivel = this->get_nivel();
	unsigned short int bad_ref = Nodo::BAD_REF;
	this->bloque->vaciar();
	this->set_nivel(nivel);
	this->primero();
	this->bloque->escribir((char*) &bad_ref, pos_act - TAM_REF, TAM_REF);
	
}
